﻿using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace Jerry.DroidBuilder.ThemeEditor.Controls
{
    public class RelativeLayout : Panel
    {
        protected override Size MeasureOverride(Size constraint)
        {
            foreach (UIElement element in base.InternalChildren)
            {
                if (element != null)
                {
                    element.Measure(constraint);
                }
            }

            // May not work correctly when wrap_content.
            return new Size();
        }

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            var childDict = (from item in Children.Cast<FrameworkElement>() select item).ToDictionary(x => (x.DataContext as LayoutInfo));
            var children = new List<LayoutInfo>(childDict.Keys);
            var elementIdDict = children.Where(x => !string.IsNullOrEmpty(x.Id)).ToDictionary(x => x.Id);
            var rectDict = new Dictionary<string, Rect>();

            // Orders the children according to the dependencies,
            // so that when do the layout calculation,
            // all the children can get all the layout precondition ready.
            OrderByDependencies(children, elementIdDict);

            foreach (var child in children)
            {
                var element = childDict[child];
                var location = new Point(0, 0);
                var edge = new Point(0, 0);

                // These four variables are used to determine
                // whether a manually dependency is set on each coordinate.
                // If no dependency is set, then, the location and width may need adjustment.
                var manuallyX = false;
                var manuallyY = false;
                var manuallyRight = false;
                var manuallyBottom = false;

                #region Location X

                var alignParentLeft = child.GetBoolean("layout_alignParentLeft", false);
                var alignLeft = child.GetValue("layout_alignLeft");
                var toRightOf = child.GetValue("layout_toRightOf");

                if (alignParentLeft)
                {
                    location.X = 0;
                    manuallyX = true;
                }
                else if (!string.IsNullOrEmpty(alignLeft))
                {
                    location.X = rectDict[alignLeft].X;
                    manuallyX = true;
                }
                else if (!string.IsNullOrEmpty(toRightOf))
                {
                    location.X = rectDict[toRightOf].Right;
                    manuallyX = true;
                }

                #endregion

                #region Location Y

                var alignParentTop = child.GetBoolean("layout_alignParentTop", false);
                var alignTop = child.GetValue("layout_alignTop");
                var below = child.GetValue("layout_below");

                if (alignParentTop)
                {
                    location.Y = 0;
                    manuallyY = true;
                }
                else if (!string.IsNullOrEmpty(alignTop))
                {
                    location.X = rectDict[alignTop].X;
                    manuallyY = true;
                }
                else if (!string.IsNullOrEmpty(below))
                {
                    location.X = rectDict[below].Bottom;
                    manuallyY = true;
                }

                #endregion

                #region Edge Right

                var alignParentRight = child.GetBoolean("layout_alignParentRight", false);
                var alignRight = child.GetValue("layout_alignRight");
                var toLeftOf = child.GetValue("layout_toLeftOf");

                if (alignParentRight)
                {
                    edge.X = arrangeSize.Width;
                    manuallyRight = true;
                }
                else if (!string.IsNullOrEmpty(alignRight))
                {
                    edge.X = rectDict[alignRight].Right;
                    manuallyRight = true;
                }
                else if (!string.IsNullOrEmpty(toLeftOf))
                {
                    edge.X = rectDict[toLeftOf].X;
                    manuallyRight = true;
                }

                #endregion

                #region Edge Bottom

                var alignParentBottom = child.GetBoolean("layout_alignParentBottom", false);
                var alignBottom = child.GetValue("layout_alignBottom");
                var above = child.GetValue("layout_above");

                if (alignParentBottom)
                {
                    edge.Y = arrangeSize.Height;
                    manuallyBottom = true;
                }
                else if (!string.IsNullOrEmpty(alignBottom))
                {
                    edge.Y = rectDict[alignBottom].Bottom;
                    manuallyBottom = true;
                }
                else if (!string.IsNullOrEmpty(above))
                {
                    edge.Y = rectDict[above].Top;
                    manuallyBottom = true;
                }

                #endregion

                var rect = new Rect(location, edge);

                #region Axis X adjustment

                if (!manuallyX || !manuallyRight)
                {
                    var desiredWidth = element.DesiredSize.Width;
                    var matchParent = (child.GetValue("layout_width") == "match_parent" || child.GetValue("layout_width") == "fill_parent");

                    if (manuallyX)
                    {
                        if (matchParent)
                        {
                            desiredWidth = arrangeSize.Width - rect.X;
                        }
                    }
                    else if (manuallyRight)
                    {
                        if (matchParent)
                        {
                            desiredWidth = rect.Right;
                        }

                        rect.X = rect.Right - desiredWidth;
                    }
                    else
                    {
                        if (matchParent)
                        {
                            desiredWidth = arrangeSize.Width;
                        }
                        else
                        {
                            var center = child.GetBoolean("layout_centerInParent", false) || child.GetBoolean("layout_centerHorizontal", false);

                            if (center)
                            {
                                rect.X = (arrangeSize.Width - desiredWidth) / 2;
                            }
                        }
                    }

                    rect.Width = desiredWidth;
                }

                #endregion

                #region Axis Y adjustment

                if (!manuallyY || !manuallyBottom)
                {
                    var desiredHeight = element.DesiredSize.Height;
                    var matchParent = (child.GetValue("layout_height") == "match_parent" || child.GetValue("layout_height") == "fill_parent");

                    if (manuallyY)
                    {
                        if (matchParent)
                        {
                            desiredHeight = arrangeSize.Height - rect.Y;
                        }
                    }
                    else if (manuallyBottom)
                    {
                        if (matchParent)
                        {
                            desiredHeight = rect.Bottom;
                        }

                        rect.Y = rect.Bottom - desiredHeight;
                    }
                    else
                    {
                        if (matchParent)
                        {
                            desiredHeight = arrangeSize.Height;
                        }
                        else
                        {
                            var center = child.GetBoolean("layout_centerInParent", false) || child.GetBoolean("layout_centerVertical", false);

                            if (center)
                            {
                                rect.Y = (arrangeSize.Height - desiredHeight) / 2;
                            }
                        }
                    }

                    rect.Height = desiredHeight;
                }

                #endregion

                // Arranges the element.
                element.Arrange(rect);

                // Add current layout info in the dictionary to later dependency usage.
                if (!string.IsNullOrEmpty(child.Id))
                {
                    rectDict.Add(child.Id, rect);
                }
            }

            return arrangeSize;
        }

        private void OrderByDependencies(List<LayoutInfo> children, Dictionary<string, LayoutInfo> dict)
        {
            var ary = children.ToArray();

            foreach (var child in ary)
            {
                var depends = GetDependencies(child, dict);

                foreach (var dep in depends)
                {
                    var index = children.IndexOf(child);
                    var depIndex = children.IndexOf(dep);

                    // Move the dependency to front of current element.
                    if (index < depIndex)
                    {
                        children.RemoveAt(depIndex);
                        children.Insert(index, dep);
                    }
                }
            }
        }

        private IEnumerable<LayoutInfo> GetDependencies(LayoutInfo child, Dictionary<string, LayoutInfo> dict)
        {
            var dependIds = new string[]
            {
                child.GetValue("layout_below"),
                child.GetValue("layout_above"),
                child.GetValue("layout_toLeftOf"),
                child.GetValue("layout_toRightOf"),
                child.GetValue("layout_alignTop"),
                child.GetValue("layout_alignLeft"),
                child.GetValue("layout_alignBottom"),
                child.GetValue("layout_alignRight"),
            };

            var depends = new List<LayoutInfo>();

            foreach (var depId in dependIds)
            {
                if (string.IsNullOrEmpty(depId))
                {
                    continue;
                }

                LayoutInfo info = null;

                if (dict.TryGetValue(depId, out info) && !depends.Contains(info))
                {
                    depends.Add(info);
                }
            }

            return depends;
        }
    }
}
